import java.awt.Color;
import java.awt.Rectangle;
import java.io.File;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedList;

import ij.ImagePlus;
import ij.gui.GenericDialog;
import ij.io.FileInfo;
import ij.io.OpenDialog;
import ij.plugin.filter.PlugInFilter;
import ij.process.ImageProcessor;

public class TextureDetection implements PlugInFilter {

	private int blockSize;
    private double maxDistance;
    private LinkedList<Region> textures;
    private int levelsNr = 32;
    private boolean useMaxDistance;

    public int init(){
        int textureNo;

        textures = new LinkedList<Region>();

        GenericDialog gd = new GenericDialog("ROSM - Texture Detection");
        gd.addNumericField("Block size: ", 64, 0);
        gd.addNumericField("Number of textures to find: ", 1, 0);
        gd.addNumericField("Maximum distance: ", 1, 2);
        gd.addCheckbox("Use maximum distance", true);
        gd.showDialog();
        if (gd.wasCanceled()) return -1;


        blockSize = (int)gd.getNextNumber();
        textureNo = (int)gd.getNextNumber();
        maxDistance = gd.getNextNumber();
        useMaxDistance = gd.getNextBoolean();


        for (int i = 0; i < textureNo; ++i) {
            OpenDialog openDialog = new OpenDialog("Wybierz plik tekstury","");
            String path = openDialog.getDirectory() + openDialog.getFileName();
            ImagePlus ip = new ImagePlus(path);

            FileInfo info = ip.getOriginalFileInfo();

            System.out.println("Info: " + info);

            if( info != null && ip.getHeight() >= blockSize && ip.getWidth() >= blockSize){
                textures.push(generateTextureRegion(ip, i));
            }else{
                System.out.println("File: " + path + "is not an image. Skipping.");
            }
        }

        return 0;
    }

	@Override
	public void run(ImageProcessor ip) {
        switch(init()){
            case -1: {
                System.out.println("Dialog was closed without using plugin.");
                return;
            }
            case -2: {
                System.out.println("No files loaded.");
                return;
            }

        }


		// podzial na bloki
		Collection<Block> blocks = divideToBlocks(ip);

		// obliczeniu kryterium dla kazdego bloku
		calculateCriteria(ip, blocks);

		// przydzial blokow do regionow na podstawie w/w kryterium
		Collection<Region> regions = allocateRegions(blocks);

		// dla kazdego bloku w regionie obliczenie macierzy spojnosci
        ImageProcessor midstep = ip.duplicate();
		for (Region region : regions) {
			for (Block block : region.blocks)
			{
				block.matrices.add(block.calculateCoocurenceMatrix(ip, levelsNr, CoocurenceMatrix.Direction.HORIZONTAL, 1).calculateParameters());
				block.matrices.add(block.calculateCoocurenceMatrix(ip, levelsNr, CoocurenceMatrix.Direction.VERTICAL, 1).calculateParameters());
				block.matrices.add(block.calculateCoocurenceMatrix(ip, levelsNr, CoocurenceMatrix.Direction.LR_SLANTED, 1).calculateParameters());
				block.matrices.add(block.calculateCoocurenceMatrix(ip, levelsNr, CoocurenceMatrix.Direction.RL_SLANTED, 1).calculateParameters());
                block.matrices.add(block.calculateCoocurenceMatrix(ip, levelsNr, CoocurenceMatrix.Direction.HORIZONTAL, 2).calculateParameters());
                block.matrices.add(block.calculateCoocurenceMatrix(ip, levelsNr, CoocurenceMatrix.Direction.VERTICAL, 2).calculateParameters());
                block.matrices.add(block.calculateCoocurenceMatrix(ip, levelsNr, CoocurenceMatrix.Direction.LR_SLANTED, 2).calculateParameters());
                block.matrices.add(block.calculateCoocurenceMatrix(ip, levelsNr, CoocurenceMatrix.Direction.RL_SLANTED, 2).calculateParameters());
			}


            region.colorRegion(midstep);
            region.generateMatrices();
            for(CoocurenceMatrix c : region.matrices){
                c.calculateParameters();
            }
		}
        new ImagePlus("Allocated regions",midstep).show();

        Region bestFit;
        double tmpDistance, bestDistance = Double.MAX_VALUE;
        int count;
        float mean = 0f;

        for(Region region : regions){
            bestFit = region;

            for(Region texture : textures){
                count = 0;
                tmpDistance = 0;
                for(CoocurenceMatrix c : texture.matrices){
                    tmpDistance += c.calculateDistanceClassifier(region.matrices.get(count),getMaxValues(regions));
                    ++count;
                }
                tmpDistance /= count;

                System.out.println(texture.id + "\t" + region.id + "\t" + tmpDistance + "\t" + region.matrices.get(0).kontrast + "\t" + region.matrices.get(1).kontrast + "\t" + region.matrices.get(2).kontrast + "\t" + region.matrices.get(3).kontrast);
                if(tmpDistance < bestDistance){
                    bestFit = texture;
                    bestDistance = tmpDistance;
                }
            }


            if(bestDistance > maxDistance && useMaxDistance){
                region.id = -1;
                region.color = Integer.MAX_VALUE;
            }else{
                region.id = bestFit.id;
                region.color = bestFit.color;
            }

            bestDistance = Double.MAX_VALUE;
            region.colorRegion(ip);
        }
	}

	@Override
	public int setup(String arg, ImagePlus imp) {
		if (arg.equals("about")) {
			showAbout();
			return DONE;
		}
		return DOES_ALL;
	}

	private void showAbout() {
		// TODO: info dialog
	}

	private Collection<Region> allocateRegions(Collection<Block> blocks) {
		//TODO: zaklada sie ze wartosci criteriaValue zawarte sa miedzy 0-1 i progujemy je na 16 wartosci
		HashMap<Integer, Region> regionMap = new HashMap<Integer, Region>();
        int i = 1;
		//double v = 1 / 31.0f;
		int r;
		for (Block block : blocks) {
			//r = (int) (block.criteriaValue / v);
			r = block.levelPixelByBrightness(levelsNr);
			if (!regionMap.containsKey(r)){
                regionMap.put(r, new Region(i++));
            }

			regionMap.get(r).blocks.add(block);
		}
		return regionMap.values();
	}

	private void calculateCriteria(ImageProcessor imageProcessor, Collection<Block> blocks) {
		for (Block block : blocks) {
			block.calculateCriteria(imageProcessor);
		}
	}
	
	private LinkedList<Block> divideToBlocks(ImageProcessor ip) {
		LinkedList<Block> blockList = new LinkedList<Block>();
		int x, y, w, h;
		Block b;
		y = 0;
		while (y < ip.getHeight()) {
			x = 0;
			while (x < ip.getWidth()) {
				w = ((blockSize + x) < ip.getWidth()) ? blockSize
						: ip.getWidth() - x;
				h = ((blockSize + y) < ip.getHeight()) ? blockSize
						: ip.getHeight() - y;
				blockList.add(new Block(x, y, w, h).setImageProcessor(ip));
				x += blockSize;
			}
			y += blockSize;
		}
		return blockList;
	}

    private Region generateTextureRegion(ImagePlus i, int id){
        Region region = new Region(i, id, divideToBlocks(i.getProcessor()));

        for (Block block : region.blocks)
        {
            block.matrices.add(block.calculateCoocurenceMatrix(i.getProcessor(), levelsNr, CoocurenceMatrix.Direction.HORIZONTAL, 1).calculateParameters());
            block.matrices.add(block.calculateCoocurenceMatrix(i.getProcessor(), levelsNr, CoocurenceMatrix.Direction.VERTICAL, 1).calculateParameters());
            block.matrices.add(block.calculateCoocurenceMatrix(i.getProcessor(), levelsNr, CoocurenceMatrix.Direction.LR_SLANTED, 1).calculateParameters());
            block.matrices.add(block.calculateCoocurenceMatrix(i.getProcessor(), levelsNr, CoocurenceMatrix.Direction.RL_SLANTED, 1).calculateParameters());
            block.matrices.add(block.calculateCoocurenceMatrix(i.getProcessor(), levelsNr, CoocurenceMatrix.Direction.HORIZONTAL, 2).calculateParameters());
            block.matrices.add(block.calculateCoocurenceMatrix(i.getProcessor(), levelsNr, CoocurenceMatrix.Direction.VERTICAL, 2).calculateParameters());
            block.matrices.add(block.calculateCoocurenceMatrix(i.getProcessor(), levelsNr, CoocurenceMatrix.Direction.LR_SLANTED, 2).calculateParameters());
            block.matrices.add(block.calculateCoocurenceMatrix(i.getProcessor(), levelsNr, CoocurenceMatrix.Direction.RL_SLANTED, 2).calculateParameters());
        }

        region.colorRegion(i.getProcessor());
        region.show();

        region.generateMatrices();
        for(CoocurenceMatrix c : region.matrices){
            c.calculateParameters();
            c.printParameters();
        }


        return region;
    }

    private HashMap<String, Double> getMaxValues(Collection<Region> regions){
        HashMap<String, Double> ret = new HashMap<String, Double>(), tmp;

        double maxKorelacja = 0, maxMoment = 0, maxKontrast = 0, maxEntropia = 0;
        for(Region r : regions){
            tmp = r.getMaxValues();
            maxKorelacja = maxKorelacja<tmp.get("korelacja")?tmp.get("korelacja"):maxKorelacja;
            maxEntropia = maxEntropia<tmp.get("entropia")?tmp.get("entropia"):maxEntropia;
            maxKontrast = maxKontrast<tmp.get("kontrast")?tmp.get("kontrast"):maxKontrast;
            maxMoment = maxMoment<tmp.get("moment")?tmp.get("moment"):maxMoment;
        }

        ret.put("korelacja",maxKorelacja);
        ret.put("kontrast",maxKontrast);
        ret.put("entropia",maxEntropia);
        ret.put("moment",maxMoment);

        return ret;
    }
}
